/*
 * Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the The Warrent Team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * This software is free for non-commercial use. You may not use this
 *       software, in whole or in part, in support of any commercial product
 *       without the express consent of the author.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

using Gdk;
using Gtk;

namespace OsHelper.Notify
{
	public static class DrawingService
	{
		const string MissingIconIcon = "application-default-icon;;application-x-executable";
		
		static DrawingService ()
		{
		}
		
		/// <summary>
		/// An empty pixbuf
		/// </summary>
		public static Pixbuf EmptyPixbuf {
			get {
				Pixbuf pb = new Pixbuf (Colorspace.Rgb, true, 8, 1, 1);
				pb.Fill (0x00000000);
				return pb;
			}
		}
		
		/// <summary>
		/// Load an icon specifying the width and height.
		/// </summary>
		/// <param name="names">
		/// A <see cref="System.String"/>
		/// </param>
		/// <param name="width">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <param name="height">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <returns>
		/// A <see cref="Gdk.Pixbuf"/>
		/// </returns>
		public static Gdk.Pixbuf LoadIcon (string names, int width, int height)
		{
			if (names == null)
				names = "";
			
			List<string> iconNames = names.Split (new [] { ";;"}, StringSplitOptions.RemoveEmptyEntries).ToList ();
			// add the MissingIconIcon as a last resort icon.
			iconNames = iconNames.Union (MissingIconIcon.Split (new [] { ";;"}, StringSplitOptions.RemoveEmptyEntries)).ToList ();
			
			Gdk.Pixbuf pixbuf = null;
			
			foreach (string name in iconNames) {
				// The icon can be loaded from a loaded assembly if the icon has
				// the format: "resource@assemblyname".
				if (IconIsEmbeddedResource (name)) {
					pixbuf = IconFromEmbeddedResource (name, width, height);
					if (pixbuf != null)
						break;
				}
				
				/*if (IconIsFile (name)) {
					pixbuf = IconFromFile (name, width, height);
					if (pixbuf != null)
						break;
				}*/
				
				if (width <= 0 || height <= 0)
					throw new ArgumentException ("Width / Height must be greater than 0 if icon is not a file or embedded resource");
				
				// Try to load icon from defaul theme.
				pixbuf = IconFromTheme (name, Math.Max (width, height), IconTheme.Default);
				if (pixbuf != null)
					break;
				
				// Try to load a generic file icon.
				if (name.StartsWith ("gnome-mime")) {
					pixbuf = GenericFileIcon (Math.Max (width, height));
					if (pixbuf != null)
						break;
				}
				
				// After this point, we assume that the caller's icon cannot be found,
				// so we warn and continue, trying the next in the list
				
				if (name != iconNames.Last ())
					OsHelper.DebugTools.Logger.Default.Notice ("Could not find '{0}', using fallback of '{1}'.", name, iconNames[ iconNames.IndexOf (name) + 1]);
			}
			
			if (pixbuf != null) {
				if (width != -1 && height != -1 && (width != pixbuf.Width || height != pixbuf.Height))
					pixbuf = pixbuf.ARScale (width, height);
				return pixbuf;
			}		
			
			// If all else fails, use an empty pixbuf.
			return EmptyPixbuf;
		}

		/// <summary>
		/// Load an icon specifying only the size.  Size will be  used for both width and height.
		/// </summary>
		/// <param name="names">
		/// A <see cref="System.String"/>
		/// </param>
		/// <param name="size">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <returns>
		/// A <see cref="Gdk.Pixbuf"/>
		/// </returns>
		public static Gdk.Pixbuf LoadIcon (string names, int size)
		{
			return LoadIcon (names, size, size);
		}
		
		/// <summary>
		/// Load an icon at its native size.  Note when this is used on icons that are not files or resources 
		/// an exception will be thrown.
		/// </summary>
		/// <param name="names">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="Gdk.Pixbuf"/>
		/// </returns>
		public static Gdk.Pixbuf LoadIcon (string names)
		{
			return LoadIcon (names, -1);
		}
		
		static bool IconIsEmbeddedResource (string name)
		{
			return 0 < name.IndexOf ("@");
		}
		
		static bool IconIsFile (string name)
		{
			return name.StartsWith ("/") ||
				   name.StartsWith ("~/") || 
				   name.StartsWith ("file://", StringComparison.OrdinalIgnoreCase);
		}
		
		static Pixbuf IconFromEmbeddedResource (string name, int width, int height)
		{
			Pixbuf pixbuf = null;
			string resource = name.Substring (0, name.IndexOf ("@"));
			string assemblyName = name.Substring (resource.Length + 1);
			
			try {
				Assembly asm = AppDomain.CurrentDomain.GetAssemblies ().First (a => a.FullName == assemblyName);
				if (asm == null)
					throw new ArgumentNullException ("Could not find assembly '{0}'.", assemblyName);
				
				if (width <= 0 || height <= 0)
					pixbuf = new Pixbuf (asm, resource);
				else
					pixbuf = new Pixbuf (asm, resource, width, height);
			} catch (Exception e) {
				OsHelper.DebugTools.Logger.Default.Warning ("Failed to load icon resource {0} from assembly {1}: {2}",
				                         resource, assemblyName, e.Message); 
				OsHelper.DebugTools.Logger.Default.Debug (e.StackTrace);
				pixbuf = null;
			}
			return pixbuf;
		}
		
		/*static Pixbuf IconFromFile (string name, int width, int height)
		{
			Pixbuf pixbuf;

			string home = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
			name = name.Replace ("~", home);
			GLib.File iconFile = (name.StartsWith ("/")) ? FileFactory.NewForPath (name) : FileFactory.NewForUri (name);
			try {
				if (width <= 0 || height <= 0)
					pixbuf = new Pixbuf (iconFile.Path);
				else
					pixbuf = new Pixbuf (iconFile.Path, width, height, true);
			} catch (Exception e) {
				Log<DrawingService>.Warn ("Error loading icon from file '" + iconFile.Path + "': " + e.Message);
				Log<DrawingService>.Debug (e.StackTrace);
				pixbuf = null;
			}
			return pixbuf;
		}*/
		
		static Pixbuf IconFromTheme (string name, int size, IconTheme theme)
		{
			Pixbuf pixbuf = null;
			string name_noext = name;
			
			// We may have to remove the extension.
			if (name.Contains (".")) {
				name_noext = name.Remove (name.LastIndexOf ("."));
			}
			
			try {
				if (theme.HasIcon (name)) {  
					pixbuf = theme.LoadIcon (name, size, 0);
				} else if (theme.HasIcon (name_noext)) { 
					pixbuf = theme.LoadIcon (name_noext, size, 0);
				} else if (name == "gnome-mime-text-plain" && theme.HasIcon ("gnome-mime-text")) { 
					pixbuf = theme.LoadIcon ("gnome-mime-text", size, 0);
				}
			} catch (Exception e) {
				OsHelper.DebugTools.Logger.Default.Warning ("Error loading themed icon '" + name + "': " + e.Message);
				OsHelper.DebugTools.Logger.Default.Debug (e.StackTrace);
				pixbuf = null;
			}
		
			return pixbuf;
		}
		
		static Pixbuf GenericFileIcon (int size)
		{
			Pixbuf pixbuf = null;
			if (IconTheme.Default.HasIcon ("gtk-file")) {
				try {
					pixbuf = IconTheme.Default.LoadIcon ("gtk-file", size, 0);
				} catch (Exception e) {
					OsHelper.DebugTools.Logger.Default.Warning ("Error loading generic icon: " + e.Message);
					OsHelper.DebugTools.Logger.Default.Debug (e.StackTrace);
					pixbuf = null;					
				}
			}
			return pixbuf;
		}
	}
}
